#include <graphics/GLSLShader.h>
#include <system/Log.h>

namespace ne
{
    GLSLShader::GLSLShader(ResourceManager *pManager,
                           const uint64_t id,
                           const std::string &name,
                           const std::string &fileName,
                           const bool isManual,
                           ManualResourceLoader *pLoader)
        : Resource(pManager, id, name, fileName, isManual, pLoader),
          GLObject(),
          mType(SHADER_TYPE_VERTEX),
          mIsValidated(false)
    {
    }

    void GLSLShader::createInternalShader(const ShaderType type)
    {
        mType = type;
        mSource = glCreateShader(mType);
    }

    void GLSLShader::freeInternalShader()
    {
        if (glIsShader(mSource))
        {
            glDeleteShader(mSource);
        }
        mSource = 0;
        mSourceCode = "";
        mSize = 0;
        mIsValidated = false;
    }

    bool GLSLShader::loadFromFile(const std::string &fileName)
    {
        FILE *file = openFile("rb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Open GLSLShader (%s) file (%s) failed", getName().c_str(), fileName.c_str());
            return false;
        }

        // Go to file end and get the size of file
        fseek(file, 0, SEEK_END);
        size_t size = ftell(file);

        fseek(file, 0, SEEK_SET);
        char *sourceCode = new char[size + 1];
        if (!fread(sourceCode, size, 1, file))
        {
            Log::Out(LL_WARNING,
                "Load GLSLShader (%s) from file (%s) failed", getName().c_str(), fileName.c_str());
            closeFile(file);
            return false;
        }
        closeFile(file);

        // Set end of the source code
        sourceCode[size] = '\0';

        bool flag = loadFromSourceCode(sourceCode);

        delete[] sourceCode;

        return flag;
    }

    bool GLSLShader::loadFromSourceCode(const std::string &sourceCode)
    {
        if (!sourceCode.length())
        {
            Log::Out(LL_WARNING,
                "Load GLSLShader (%s) from Source Code failed", getName().c_str());
            return false;
        }

        if (mIsManual)
        {
            mSourceCode = sourceCode;
        }

        const char *sourceCodePtr = sourceCode.c_str();
        glShaderSource(mSource, 1, &sourceCodePtr, 0);
        glCompileShader(mSource);

        // Check compile error
        int success = 0;
        glGetShaderiv(mSource, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            Log::Out(LL_WARNING,
                "Compile GLSLShader (%s) from Source Code failed : %s", getName().c_str(), getInfoLog().c_str());
            return false;
        }

        mSize = sizeof(char) * sourceCode.length();
        mIsValidated = true;

        return true;
    }

    bool GLSLShader::isShader() const
    {
        GLboolean res = glIsShader(mSource);
        if (res)
        {
            return true;
        }
        return false;
    }

    bool GLSLShader::isValidated() const
    {
        return mIsValidated;
    }

    ShaderType GLSLShader::getType() const
    {
        return mType;
    }

    std::string GLSLShader::getSourceCode() const
    {
        return mSourceCode;
    }

    std::string GLSLShader::getInfoLog() const
    {
        if (isShader())
        {
            int infoLength = 0;
            glGetShaderiv(mSource, GL_INFO_LOG_LENGTH, &infoLength);
            char *info = new char[infoLength + 1];
            glGetShaderInfoLog(mSource, infoLength, 0, info);
            std::string infoLog = info;
            delete[] info;
            return infoLog;
        }
        return "";
    }

    GLSLShader::~GLSLShader()
    {
        unload();
    }

    bool GLSLShader::loadImplement()
    {
        createInternalShader(mType);
        return loadFromFile(getFileName());
    }

    bool GLSLShader::unloadImplement()
    {
        freeInternalShader();
        return true;
    }

    bool GLSLShader::downloadImplement()
    {
        if (!mSourceCode.length())
        {
            return false;
        }

        FILE *file = openFile("wb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Create GLSLShader (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        if (!fwrite(mSourceCode.c_str(), sizeof(char)*mSourceCode.length(), 1, file))
        {
            Log::Out(LL_WARNING,
                "Save GLSLShader (%s) to file (%s) failed", getName().c_str(), getFileName().c_str());
            closeFile(file);
            return false;
        }
        closeFile(file);

        return true;
    }

    size_t GLSLShader::_calculateSize() const
    {
        return mSize;
    }
}
